#include "D3DApp.h"
#include <ctime>
#include <fstream>
#include <iostream>
#include <windowsx.h>

//------------------------------------------------------------------------------
// Static member definitions
//------------------------------------------------------------------------------
std::wstring D3DApp::fatalMessage(L"A fatal error has occurred and the application will be shut down.");
const std::wstring D3DApp::WINDOW_CLASS_NAME = L"D3DAppWindowClass";

//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
D3DApp::D3DApp():
	d3dDepthStencilBuffer(NULL),
	d3dDepthStencilView(NULL),
	d3dDevice(NULL),
	d3dDeviceContext(NULL),
	d3dRenderTargetView(NULL),
	deltaTime(0.0f),
	dxgiSwapChain(NULL),
	hWndIcon(NULL),
	hWndCursor(NULL),
	hInstance(GetModuleHandle(NULL)),
	hWnd(NULL),
	paused(false),
	returnCode(0),
	runCalled(false),
	timerLastTick(0),
	timerPauseTick(0),
	timerPeriod(0.0f),
	windowHeight(480),
	windowWidth(640),
	wndProcErrorCode(S_OK)
{
	// Set a global callback function to log all unhandled exceptions
	SetUnhandledExceptionFilter(LogUnhandledException);
}

//------------------------------------------------------------------------------
// Destructor
//------------------------------------------------------------------------------
D3DApp::~D3DApp()
{
	ReleaseCOM(d3dDepthStencilView);
	ReleaseCOM(d3dDepthStencilBuffer);
	ReleaseCOM(d3dRenderTargetView);
	ReleaseCOM(d3dDeviceContext);
	ReleaseCOM(d3dDevice);
	ReleaseCOM(dxgiSwapChain);
	if (hWnd) { DestroyWindow(hWnd); hWnd = NULL; }
	UnregisterClass(WINDOW_CLASS_NAME.c_str(), NULL);
}

//------------------------------------------------------------------------------
// Helper routine for creating render target view, depth/stencil view, and viewport
//------------------------------------------------------------------------------
HRESULT D3DApp::CreateRenderTargets()
{
	// Get a reference to the back buffer, for creating the render target view
	ID3D11Texture2D *backBuffer;
	HRESULT hr = dxgiSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void **>(&backBuffer));
	if (FAILED(hr))
		return HR_TRACE(hr, L"IDXGISwapChain::GetBuffer");

	// Create the render target view
	hr = d3dDevice->CreateRenderTargetView(backBuffer, NULL, &d3dRenderTargetView);
	backBuffer->Release();
	if (FAILED(hr))
		return HR_TRACE(hr, L"ID3D11Device::CreateRenderTargetView");

	// Create the depth/stencil buffer
	D3D11_TEXTURE2D_DESC dsBufDesc;
	dsBufDesc.Width = windowWidth;
	dsBufDesc.Height = windowHeight;
	dsBufDesc.MipLevels = 1;
	dsBufDesc.ArraySize = 1;
	dsBufDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
	dsBufDesc.SampleDesc.Count = 1;
	dsBufDesc.SampleDesc.Quality = 0;
	dsBufDesc.Usage = D3D11_USAGE_DEFAULT;
	dsBufDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
	dsBufDesc.CPUAccessFlags = 0;
	dsBufDesc.MiscFlags = 0;
	hr = d3dDevice->CreateTexture2D(&dsBufDesc, NULL, &d3dDepthStencilBuffer);
	if (FAILED(hr))
		return HR_TRACE(hr, L"ID3D11Device::CreateTexture2D");

	// Create the depth/stencil view
	hr = d3dDevice->CreateDepthStencilView(d3dDepthStencilBuffer, NULL, &d3dDepthStencilView);
	if (FAILED(hr))
		return HR_TRACE(hr, L"ID3D11Device::CreateDepthStencilView");

	// Bind the render targets
	d3dDeviceContext->OMSetRenderTargets(1, &d3dRenderTargetView, d3dDepthStencilView);

	// Set the viewport
	D3D11_VIEWPORT viewport;
	viewport.TopLeftX = 0;
	viewport.TopLeftY = 0;
	viewport.Width = static_cast<FLOAT>(windowWidth);
	viewport.Height = static_cast<FLOAT>(windowHeight);
	viewport.MinDepth = 0.0f;
	viewport.MaxDepth = 1.0f;
	d3dDeviceContext->RSSetViewports(1, &viewport);

	return S_OK;
}

//------------------------------------------------------------------------------
// Tells the user that an error occurred and where to find the log file
//------------------------------------------------------------------------------
void D3DApp::DisplayErrorMessage()
{
	MessageBox(NULL, fatalMessage.c_str(), L"Fatal Error", MB_ICONERROR);
}

//------------------------------------------------------------------------------
// Wrapper around DrawFrame()
//------------------------------------------------------------------------------
HRESULT D3DApp::DrawFrame()
{
	if (!paused)
	{
		// Get current counter value
		__int64 timerTick;
		QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER *>(&timerTick));

		// Update delta time
		deltaTime = (timerTick - timerLastTick) * timerPeriod;
		timerLastTick = timerTick;

		// Render the frame
		OnDrawFrame();

		// Present the frame
		HRESULT hr = dxgiSwapChain->Present(0, 0);
		if (FAILED(hr))
			return HR_TRACE(hr, L"IDXGISwapChain::Present");
	}
	
	return S_OK;
}

//------------------------------------------------------------------------------
// Internally called by Run2() to set up the main window and Direct3D. Guaranteed
// to only be invoked once per instance of D3DApp
//------------------------------------------------------------------------------
HRESULT D3DApp::Initialize()
{
	// This is required to guarantee that QueryPerformanceCounter() behaves. It only lets the
	// thread run on the first CPU core
	if (!SetThreadAffinityMask(GetCurrentThread(), 0x1))
		return HR_TRACE(HRESULT_FROM_WIN32(GetLastError()), L"SetThreadAffinityMask");

	// Get performance counter frequency
	__int64 timerFreq;
	if (!QueryPerformanceFrequency(reinterpret_cast<LARGE_INTEGER *>(&timerFreq)))
		return HR_TRACE(HRESULT_FROM_WIN32(GetLastError()), L"QueryPerformanceFrequency");
	timerPeriod = 1.0f / timerFreq;

	// Register the window class
	WNDCLASS wc;
	wc.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
	wc.lpfnWndProc = WindowProcedure;
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hInstance = hInstance;
	wc.hIcon = hWndIcon ? hWndIcon : LoadIcon(NULL, IDI_APPLICATION);
	wc.hCursor = hWndCursor ? hWndCursor : LoadCursor(NULL, IDC_ARROW);
	wc.hbrBackground = NULL;
	wc.lpszMenuName = NULL;
	wc.lpszClassName = WINDOW_CLASS_NAME.c_str();

	if (!RegisterClass(&wc))
	{
		DWORD dwError = GetLastError();

		// The class already exists? No problem!
		if (dwError != ERROR_CLASS_ALREADY_EXISTS)
			return HR_TRACE(HRESULT_FROM_WIN32(dwError), L"RegisterClass");
	}

	// Calculate window dimensions based on client area dimensions
	RECT r = {0, 0, windowWidth, windowHeight};
	AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, FALSE);

	// Create the window. The instance of this D3DApp object is passed through lpParam and will
	// be used when processing WM_CREATE.
	hWnd = CreateWindow(WINDOW_CLASS_NAME.c_str(), windowCaption.c_str(), WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, 0, r.right - r.left, r.bottom - r.top, NULL, NULL, hInstance, this);

	if (!hWnd)
		return HR_TRACE(HRESULT_FROM_WIN32(GetLastError()), L"CreateWindow");

	// Attach this D3DApp object to the window
	SetLastError(0);
	if (!SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this)) && GetLastError())
		return HR_TRACE(HRESULT_FROM_WIN32(GetLastError()), L"SetWindowLongPtr");

	// Set Direct3D device flags
	UINT d3dCreateFlags = D3D11_CREATE_DEVICE_SINGLETHREADED;
#ifdef _DEBUG
	d3dCreateFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

	// Fill out swap chain info
	DXGI_SWAP_CHAIN_DESC swapDesc;
	swapDesc.BufferDesc.Width = windowWidth;
	swapDesc.BufferDesc.Height = windowHeight;
	swapDesc.BufferDesc.RefreshRate.Numerator = 60;
	swapDesc.BufferDesc.RefreshRate.Denominator = 1;
	swapDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
	swapDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
	swapDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
	swapDesc.SampleDesc.Count = 1;
	swapDesc.SampleDesc.Quality = 0;
	swapDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
	swapDesc.BufferCount = 1;
	swapDesc.OutputWindow = hWnd;
	swapDesc.Windowed = TRUE;
	swapDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
	swapDesc.Flags = 0;

	// Create the Direct3D device and swap chain
	D3D_FEATURE_LEVEL featureLevels[] = {D3D_FEATURE_LEVEL_9_1};
	D3D_FEATURE_LEVEL featureLevel;
	HRESULT hr = D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL,
		d3dCreateFlags, featureLevels, sizeof(featureLevels) / sizeof(D3D_FEATURE_LEVEL), D3D11_SDK_VERSION,
		&swapDesc, &dxgiSwapChain, &d3dDevice, &featureLevel, &d3dDeviceContext);
	if (FAILED(hr))
		return HR_TRACE(hr, L"D3D11CreateDeviceAndSwapChain");

	// Set initial timer value
	QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER *>(&timerLastTick));

	// Invoke subclass initializer
	hr = OnInitialize();
	if (FAILED(hr))
		return hr;

	return S_OK;
}

//------------------------------------------------------------------------------
// This will append a message to the log file, along with a date/time
// Example: LogMessage(L"MyApp.log") << L"An error has occurred" << endl;
//------------------------------------------------------------------------------
std::wostream & D3DApp::LogMessage()
{
	static bool attemptedOpen = false;
	static std::wofstream logFile;

	// Open log file, if it hasn't been already
	if (!attemptedOpen)
	{
		attemptedOpen = true;

		// Get full path to current EXE
		DWORD nSize = MAX_PATH, ret;
		wchar_t * buf = NULL;
		do
		{
			delete [] buf;
			buf = new wchar_t[nSize];
			ret = GetModuleFileName(NULL, buf, nSize);
			nSize *= 4;
		}
		while (ret && GetLastError() == ERROR_INSUFFICIENT_BUFFER);

		// Use the returned path to open a log file
		if (ret)
			logFile.open((std::wstring(buf) + L".log").c_str(), std::ios::app);

		delete [] buf;
	}

	// If the error log is in an error state, use wcerr
	std::wostream & logf = !logFile ? std::wcerr : logFile;

	// Write timestamp
	std::time_t t = time(NULL);
	struct tm now;
	if (!localtime_s(&now, &t))
	{
		logf << std::dec << std::setfill(L'0')
			<< std::setw(4) << now.tm_year + 1900 << L"-"
			<< std::setw(2) << now.tm_mon + 1 << L"-"
			<< std::setw(2) << now.tm_mday << L" "
			<< std::setw(2) << now.tm_hour << L":"
			<< std::setw(2) << now.tm_min << L":"
			<< std::setw(2) << now.tm_sec << L" ";
	}

	return logf;
}

//------------------------------------------------------------------------------
// Called to log erroneous HRESULT values obtained from Windows APIs. Use the macro HR_TRACE()
// to invoke this method rather than calling it directly
//------------------------------------------------------------------------------
HRESULT D3DApp::LogTraceMessage(const std::wstring &file, int line, HRESULT hr, const std::wstring &str)
{
	LogMessage() << std::setfill(L'0')
		<< L"Trace: " << str
		<< L"; File: " << file << L"(" << std::dec << line
		<< L"); HRESULT: " << std::hex << std::setw(sizeof(hr)*2) << hr
		<< std::endl;

	return hr;
}

//------------------------------------------------------------------------------
// This is called by the OS when an unhandled structured exception or C++ exception occurs. It will
// log the error the the file using D3DApp::LogMessage()
//------------------------------------------------------------------------------
LONG WINAPI D3DApp::LogUnhandledException(LPEXCEPTION_POINTERS lpExceptionPointers)
{
	char *moduleBase = reinterpret_cast<char *>(GetModuleHandle(NULL));

	// Write exception information to log file, iterating through the chain of inner exceptions
	PEXCEPTION_RECORD pEx = lpExceptionPointers->ExceptionRecord;
	while (pEx)
	{
		std::wostream &log = LogMessage();
		log << std::hex << std::uppercase << std::setfill(L'0')
			<< L"Exception; Code: " << std::setw(sizeof(pEx->ExceptionCode)*2) << pEx->ExceptionCode
			<< L"; Flags: " << std::setw(sizeof(pEx->ExceptionFlags)*2) << pEx->ExceptionFlags
			<< L"; Address: " << std::setw(sizeof(pEx->ExceptionAddress)*2)
			<< static_cast<char *>(pEx->ExceptionAddress) - moduleBase;

		for (DWORD i = 0; i < pEx->NumberParameters; ++i)
			log << std::dec << L"; Parameter " << i << L": "
				<< std::hex << std::setw(sizeof(pEx->ExceptionInformation[i])*2) << pEx->ExceptionInformation[i];

		log << std::endl;

		pEx = pEx->ExceptionRecord;
	}

	DisplayErrorMessage();

	return EXCEPTION_EXECUTE_HANDLER;
}

//------------------------------------------------------------------------------
// Called internally to pause the application
//------------------------------------------------------------------------------
void D3DApp::Pause()
{
	paused = true;
	QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER *>(&timerPauseTick));
}

//------------------------------------------------------------------------------
// Called to re-size the swap chain buffers when necessary
//------------------------------------------------------------------------------
HRESULT D3DApp::Resize()
{
	// Release references to swap chain buffers
	d3dDeviceContext->OMSetRenderTargets(0, NULL, NULL);
	ReleaseCOM(d3dRenderTargetView);
	ReleaseCOM(d3dDepthStencilView);
	ReleaseCOM(d3dDepthStencilBuffer);

	// Resize the backbuffers
	HRESULT hr = dxgiSwapChain->ResizeBuffers(0, windowWidth, windowHeight, DXGI_FORMAT_UNKNOWN, 0);
	if (FAILED(hr))
		return HR_TRACE(hr, L"IDXGISwapChain::ResizeBuffers");

	// Create new render target views
	hr = CreateRenderTargets();
	if (FAILED(hr))
		return hr;

	return OnResize();
}

//------------------------------------------------------------------------------
// Wrapper around Run2()
//------------------------------------------------------------------------------
int D3DApp::Run()
{
	// Prevent double app executions
	if (runCalled)
		return returnCode;
	runCalled = true;

	// Invoke second layer run method
	returnCode = Run2();
	if (returnCode)
		DisplayErrorMessage();

	return returnCode;
}

//------------------------------------------------------------------------------
// Initializes the application window, enters the Windows message loop until closed
//------------------------------------------------------------------------------
int D3DApp::Run2()
{
	HRESULT hr;

	if (FAILED(hr = Initialize()))
		return hr;

	// Display the app window and immediately render a frame to it
	ShowWindow(hWnd, SW_SHOW);
	UpdateWindow(hWnd);

	// Main application loop
	MSG msg;
	while (true)
	{
		// Pause loop, allows application to relinquish CPU while still being responsive
		while (paused && GetMessage(&msg, NULL, 0, 0))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);

			if (FAILED(wndProcErrorCode))
				return wndProcErrorCode;
		}

		// Main message loop, render frames if no messages
		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			if (msg.message == WM_QUIT)
				break;

			TranslateMessage(&msg);
			DispatchMessage(&msg);

			if (FAILED(wndProcErrorCode))
				return wndProcErrorCode;
		}
		else
		{
			if (FAILED(hr = DrawFrame()))
				return hr;
		}
	}

	return msg.wParam;
}

//------------------------------------------------------------------------------
// Window procedure, processes messages for the application window
//------------------------------------------------------------------------------
LRESULT D3DApp::ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg)
	{
	case WM_DESTROY:
		// Clean up hWnd variable, post WM_QUIT message
		this->hWnd = NULL;
		PostQuitMessage(0);
		return 0;

	case WM_GETMINMAXINFO:
		// Limit minimum dimensions of the window
		reinterpret_cast<MINMAXINFO *>(lParam)->ptMinTrackSize.x = MIN_WINDOW_WIDTH;
		reinterpret_cast<MINMAXINFO *>(lParam)->ptMinTrackSize.y = MIN_WINDOW_HEIGHT;
		return 0;

	case WM_LBUTTONDOWN:
		wndProcErrorCode = OnLeftMouseDown(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
		return 0;

	case WM_LBUTTONUP:
		wndProcErrorCode = OnLeftMouseUp(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
		return 0;

	case WM_MOUSEMOVE:
		wndProcErrorCode = OnMouseMove(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
		return 0;

	case WM_MOUSEWHEEL:
		wndProcErrorCode = OnMouseWheel(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), (float)GET_WHEEL_DELTA_WPARAM(wParam) / WHEEL_DELTA);
		return 0;
		
	case WM_MOVE:
		// Moving windows are caught in some internal Win32 loop, so render as it is moved
		DrawFrame();
		return 0;

	case WM_PAINT:
		// No harm in drawing a frame in response to WM_PAINT, just make sure to inform Windows
		// that the window has been redrawn with ValidateRect()
		DrawFrame();
		ValidateRect(hWnd, NULL);
		return 0;

	case WM_RBUTTONDOWN:
		wndProcErrorCode = OnRightMouseDown(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
		return 0;

	case WM_RBUTTONUP:
		wndProcErrorCode = OnRightMouseUp(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
		return 0;

	case WM_SIZE:
		// If window is in a non-minimized state, make sure the app is not paused and resize
		// all buffers tied to the swap chain
		if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED)
		{
			Unpause();

			// Resize swap chain buffers
			windowWidth = LOWORD(lParam);
			windowHeight = HIWORD(lParam);
			wndProcErrorCode = Resize();
		}
		// Pause here, relinquishing CPU resources
		else if (wParam == SIZE_MINIMIZED)
			Pause();

		return 0;
	}

	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

//------------------------------------------------------------------------------
// Called internally to unpause the application
//------------------------------------------------------------------------------
void D3DApp::Unpause()
{
	if (paused)
	{
		paused = false;

		__int64 timerUnpauseTick;
		QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER *>(&timerUnpauseTick));

		// Add pause tick count to last tick to "erase" the pause from happening
		timerLastTick = timerLastTick + (timerUnpauseTick - timerPauseTick);
	}
}

//------------------------------------------------------------------------------
// Wrapper around the true window procedure, ProcessWindowMessage()
//------------------------------------------------------------------------------
LRESULT CALLBACK D3DApp::WindowProcedure(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	return (reinterpret_cast<D3DApp *>(GetWindowLongPtr(hWnd, GWLP_USERDATA)))->ProcessWindowMessage(hWnd, uMsg, wParam, lParam);
}